package cn.iocoder.yudao.module.hs.service.qcorder;

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.hs.controller.admin.qchistory.vo.QcHistorySaveReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.qcorder.vo.QcOrderPageReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.qcorder.vo.QcOrderRespVO;
import cn.iocoder.yudao.module.hs.controller.admin.qcorder.vo.QcOrderSaveReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.qcorderdetail.vo.QcOrderDetailSaveReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.rfid.vo.RfidSaveReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.tray.vo.TraySaveReqVO;
import cn.iocoder.yudao.module.hs.dal.dataobject.material.MaterialDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.materialbox.MaterialBoxDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.materialmode.MaterialModeDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.qchistory.QcHistoryDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.qcorder.QcOrderDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.qcorderdetail.QcOrderDetailDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.receiveorder.ReceiveOrderDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.receiveorderdetail.ReceiveOrderDetailDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.rfid.RfidDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.tray.TrayDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.warehouse.WarehouseDO;
import cn.iocoder.yudao.module.hs.dal.mysql.material.MaterialMapper;
import cn.iocoder.yudao.module.hs.dal.mysql.qchistory.QcHistoryMapper;
import cn.iocoder.yudao.module.hs.dal.mysql.qcorder.QcOrderMapper;
import cn.iocoder.yudao.module.hs.dal.mysql.qcorderdetail.QcOrderDetailMapper;
import cn.iocoder.yudao.module.hs.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.hs.dal.redis.no.OrderNoRedisDAO;
import cn.iocoder.yudao.module.hs.enums.material.MaterialOperateTypeEnum;
import cn.iocoder.yudao.module.hs.enums.materialmode.MaterialModeConstants;
import cn.iocoder.yudao.module.hs.enums.qcorder.QcOrderResultEnum;
import cn.iocoder.yudao.module.hs.enums.qcorder.QcResultEnum;
import cn.iocoder.yudao.module.hs.enums.receiveorder.ReceiveOrderStatus;
import cn.iocoder.yudao.module.hs.enums.warehouse.WarehouseStatusEnum;
import cn.iocoder.yudao.module.hs.framework.core.annotations.MaterialLog;
import cn.iocoder.yudao.module.hs.framework.core.utils.MaterialLogUtils;
import cn.iocoder.yudao.module.hs.service.material.MaterialService;
import cn.iocoder.yudao.module.hs.service.materialbox.MaterialBoxService;
import cn.iocoder.yudao.module.hs.service.materialmode.MaterialModeService;
import cn.iocoder.yudao.module.hs.service.qcorderdetail.QcOrderDetailService;
import cn.iocoder.yudao.module.hs.service.receiveorder.ReceiveOrderService;
import cn.iocoder.yudao.module.hs.service.receiveorderdetail.ReceiveOrderDetailService;
import cn.iocoder.yudao.module.hs.service.rfid.RfidService;
import cn.iocoder.yudao.module.hs.service.tray.TrayService;
import cn.iocoder.yudao.module.hs.service.warehouse.WarehouseService;
import cn.iocoder.yudao.module.hs.util.RandomUtil;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.hs.enums.ErrorCodeConstants.*;

/**
 * 质检单信息 Service 实现类
 *
 * @author 航盛
 */
@Service
@Validated
public class QcOrderServiceImpl implements QcOrderService {

    @Resource
    private QcOrderMapper qcOrderMapper;
    @Resource
    private OrderNoRedisDAO orderNoRedisDAO;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private QcOrderDetailMapper qcOrderDetailMapper;
    @Resource
    private RfidService rfidService;
    @Resource
    private MaterialService materialService;
    @Resource
    private QcOrderDetailService qcOrderDetailService;
    @Resource
    private QcHistoryMapper qcHistoryMapper;
    @Resource
    private TrayService trayService;
    @Resource
    private MaterialModeService materialModeService;
    @Resource
    private WarehouseService warehouseService;
    @Resource
    private MaterialMapper materialMapper;
    @Resource
    private MaterialBoxService materialBoxService;
    @Resource
    private ReceiveOrderDetailService receiveOrderDetailService;
    @Resource
    private ReceiveOrderService receiveOrderService;

    @Override
    @Transactional
    @MaterialLog(operateType = MaterialOperateTypeEnum.THIRD_INSPECTION)
    public Long createQcOrder(QcOrderSaveReqVO createReqVO) {
        if (CollUtil.isEmpty(createReqVO.getQcDetailList())) {
            throw exception(QC_ORDER_DETAIL_NOT_EXISTS);
        }
        MaterialBoxDO materialBox = materialBoxService.getMaterialBox(createReqVO.getBoxCode());
        if (materialBox == null) {
            throw exception(MATERIAL_BOX_NOT_EXISTS);
        }
        QcOrderDetailSaveReqVO qcOrderDetailSaveReqVO = createReqVO.getQcDetailList().get(0);
        ReceiveOrderDetailDO receiveOrderDetail = receiveOrderDetailService.getReceiveOrderDetail(qcOrderDetailSaveReqVO.getMaterialCode());
        if (receiveOrderDetail == null) {
            throw exception(RECEIVE_ORDER_DETAIL_NOT_EXISTS);
        }
        ReceiveOrderDO receiveOrder = receiveOrderService.getReceiveOrder(receiveOrderDetail.getOrderId());
        if (receiveOrder == null) {
            throw exception(RECEIVE_ORDER_NOT_EXISTS);
        }
        if (!ReceiveOrderStatus.RECEIVED.getStatus().equals(receiveOrder.getStatus())) {
            throw exception(RECEIVE_ORDER_STATUS_ERROR, receiveOrder.getNo());
        }
        AdminUserRespDTO user = adminUserApi.getUser(getLoginUserId());
        WarehouseDO warehouse = warehouseService.getWarehouse(user.getWarehouseId());
        // 插入
        QcOrderDO qcOrder = BeanUtils.toBean(createReqVO, QcOrderDO.class);
        qcOrder.setCode(orderNoRedisDAO.generate(OrderNoRedisDAO.QC_ORDER_NO_PREFIX, RedisKeyConstants.QC_ORDER_NO));
        qcOrder.setWarehouseId(user.getWarehouseId());
        qcOrder.setDeptId(warehouse.getDeptId());
        qcOrder.setOperator(String.valueOf(getLoginUserId()));
        qcOrder.setQcCount(createReqVO.getQcDetailList().size());
        qcOrderMapper.insert(qcOrder);

        List<QcHistorySaveReqVO> qcHistorySaveReqVOList = new ArrayList<>();
        // 用于记录物料日志
        List<Long> materialIds = new ArrayList<>();
        for (QcOrderDetailSaveReqVO qcOrderDetail : createReqVO.getQcDetailList()) {
            MaterialDO materialDO = materialService.getMaterial(qcOrderDetail.getMaterialCode());
            if (materialDO == null) {
                throw exception(MATERIAL_NOT_EXISTS);
            }
            MaterialModeDO materialMode = materialModeService.getMaterialMode(materialDO.getModeCode(), user.getWarehouseId());
            if (materialMode == null) {
                throw exception(MATERIAL_MODE_NOT_EXISTS_NO_QC);
            }
            if (MaterialModeConstants.LANDING_QC_NO.equals(materialMode.getLandingQc())) {
                throw exception(MATERIAL_MODE_LANDING_QC_NO);
            }
            List<MaterialDO> materialListByBoxCode = materialService.getMaterialListByBoxCode(createReqVO.getBoxCode());
            List<QcOrderDetailSaveReqVO> notInMaterialList = createReqVO.getQcDetailList().stream()
                    .filter(detail -> materialListByBoxCode.stream()
                            .noneMatch(material -> material.getCode().equals(detail.getMaterialCode())))
                    .filter(detail -> "0".equals(detail.getQcResult()))
                    .collect(Collectors.toList());
            if (notInMaterialList.size() + materialListByBoxCode.size()  > materialMode.getBoxCapacityCount()) {
                throw exception(MATERIAL_BOX_CAPACITY_ERROR);
            }
            // 判断是否已经入库，如已入库则记录入库质检
            if (WarehouseStatusEnum.ENTRY.getStatus().equals(materialDO.getStatus())) {
                qcOrderDetail.setIsEntry(1);
            } else {
                qcOrderDetail.setIsEntry(0);
            }
            qcOrderDetail.setMaterialName(materialDO.getName());
            qcOrderDetail.setMaterialModeCode(materialDO.getModeCode());
            qcOrderDetail.setQcOrderId(qcOrder.getId());
            if (QcResultEnum.GOOD.getType().equals(qcOrderDetail.getQcResult())) {
                qcOrderDetail.setBoxCode(createReqVO.getBoxCode());
            }
            TrayDO trayDO = createTrayAndRfid(warehouse.getId(), warehouse.getDeptId(), qcOrderDetail.getInfoCode());
            materialService.updateMaterialQcResult(trayDO.getCode(), createReqVO.getBoxCode(), qcOrderDetail.getQcResult(), qcOrderDetail.getMaterialCode());
            // 创建质检历史记录
            buildQcHistoryList(qcOrderDetail, qcOrder.getCode(), qcOrder.getWarehouseId(), warehouse.getDeptId(), qcOrder.getOperator(), qcHistorySaveReqVOList, materialBox.getCode());
            materialIds.add(materialDO.getId());
        }
        // 更新物料托盘编码，清空物料箱编码，质检结果
        updateTripartiteFifoTime(createReqVO.getBoxCode());
        // 批量插入物料质检结果
        qcOrderDetailMapper.insertBatch(BeanUtils.toBean(createReqVO.getQcDetailList(), QcOrderDetailDO.class));
        // 批量插入物料质检记录
        qcHistoryMapper.insertBatch(BeanUtils.toBean(qcHistorySaveReqVOList, QcHistoryDO.class));
        // 记录物料日志
        MaterialLogUtils.setMaterialInfo(materialIds);
        // 返回
        return qcOrder.getId();
    }

    @Override
    @Transactional
    @MaterialLog(operateType = MaterialOperateTypeEnum.THIRD_DEFECT_HANDLE)
    public Long defectHandle(QcOrderSaveReqVO createReqVO) {
        if (CollUtil.isEmpty(createReqVO.getQcDetailList())) {
            throw exception(QC_ORDER_DETAIL_NOT_EXISTS);
        }
        AdminUserRespDTO user = adminUserApi.getUser(getLoginUserId());
        WarehouseDO warehouse = warehouseService.getWarehouse(user.getWarehouseId());
        // 插入
        QcOrderDO qcOrder = BeanUtils.toBean(createReqVO, QcOrderDO.class);
        qcOrder.setCode(orderNoRedisDAO.generate(OrderNoRedisDAO.QC_ORDER_NO_PREFIX, RedisKeyConstants.QC_ORDER_NO));
        qcOrder.setWarehouseId(user.getWarehouseId());
        qcOrder.setDeptId(warehouse.getDeptId());
        qcOrder.setOperator(String.valueOf(getLoginUserId()));
        qcOrder.setQcCount(createReqVO.getQcDetailList().size());
        qcOrderMapper.insert(qcOrder);

        List<QcHistorySaveReqVO> qcHistorySaveReqVOList = new ArrayList<>();
        // 用于记录物料日志
        List<Long> materialIds = new ArrayList<>();
        for (QcOrderDetailSaveReqVO qcOrderDetail : createReqVO.getQcDetailList()) {
            MaterialDO materialDO = materialService.getMaterial(qcOrderDetail.getMaterialCode());
            if (materialDO == null) {
                throw exception(MATERIAL_NOT_EXISTS);
            }
            // 判断是否已经入库，如已入库则记录入库质检
            if (WarehouseStatusEnum.ENTRY.getStatus().equals(materialDO.getStatus())) {
                qcOrderDetail.setIsEntry(1);
            } else {
                qcOrderDetail.setIsEntry(0);
            }
            qcOrderDetail.setMaterialName(materialDO.getName());
            qcOrderDetail.setMaterialModeCode(materialDO.getModeCode());
            qcOrderDetail.setQcOrderId(qcOrder.getId());
            if (QcResultEnum.GOOD.getType().equals(qcOrderDetail.getQcResult())) {
                TrayDO trayDO = createTrayAndRfid(warehouse.getId(), warehouse.getDeptId(), qcOrderDetail.getInfoCode());
                materialService.updateMaterialQcResult(trayDO.getCode(), null, qcOrderDetail.getQcResult(), qcOrderDetail.getMaterialCode());
            } else {
                materialService.updateMaterialQcResult(null, null, qcOrderDetail.getQcResult(), qcOrderDetail.getMaterialCode());
            }
            // 创建质检历史记录
            buildQcHistoryList(qcOrderDetail, qcOrder.getCode(), qcOrder.getWarehouseId(), warehouse.getDeptId(), qcOrder.getOperator(), qcHistorySaveReqVOList, null);
            // 更新物料托盘编码，清空物料箱编码，质检结果
            materialIds.add(materialDO.getId());
        }
        // 批量插入物料质检结果
        qcOrderDetailMapper.insertBatch(BeanUtils.toBean(createReqVO.getQcDetailList(), QcOrderDetailDO.class));
        // 批量插入物料质检记录
        qcHistoryMapper.insertBatch(BeanUtils.toBean(qcHistorySaveReqVOList, QcHistoryDO.class));
        // 记录物料日志
        MaterialLogUtils.setMaterialInfo(materialIds);
        // 返回
        return qcOrder.getId();
    }

    @Override
    public void deleteQcOrder(Long id) {
        // 校验存在
        validateQcOrderExists(id);
        // 删除
        qcOrderMapper.deleteById(id);
    }

    private void validateQcOrderExists(Long id) {
        if (qcOrderMapper.selectById(id) == null) {
            throw exception(QC_ORDER_NOT_EXISTS);
        }
    }

    @Override
    public QcOrderDO getQcOrder(Long id) {
        return qcOrderMapper.selectById(id);
    }

    @Override
    public PageResult<QcOrderRespVO> getQcOrderPage(QcOrderPageReqVO pageReqVO) {
        PageResult<QcOrderDO> qcOrderDOPageResult = qcOrderMapper.selectPage(pageReqVO);
        if (CollUtil.isEmpty(qcOrderDOPageResult.getList())) {
            return PageResult.empty();
        }
        List<AdminUserRespDTO> userList = adminUserApi.getUserList();
        // 获取用户列表并构建 userId -> nickname 的映射
        Map<Long, String> userNicknameMap = userList.stream()
                .collect(Collectors.toMap(AdminUserRespDTO::getId, AdminUserRespDTO::getNickname));

        PageResult<QcOrderRespVO> qcOrderRespVOPageResult = BeanUtils.toBean(qcOrderDOPageResult, QcOrderRespVO.class);
        // 遍历 qcOrderRespVOPageResult.getList() 并赋值 creatorName
        qcOrderRespVOPageResult.getList().forEach(qcOrderRespVO -> {
            // 匹配 creator 对应的 nickname
            if (qcOrderRespVO.getOperator() != null && userNicknameMap.containsKey(Long.valueOf(qcOrderRespVO.getOperator()))) {
                qcOrderRespVO.setOperatorName(userNicknameMap.get(Long.valueOf(qcOrderRespVO.getOperator())));
            }
            List<QcOrderDetailDO> qcOrderDetailList = qcOrderDetailService.getQcOrderDetailList(qcOrderRespVO.getId());
            long count = Optional.ofNullable(qcOrderDetailList).orElse(Collections.emptyList()).stream()
                    .filter(detail -> "1".equals(detail.getQcResult()) || "2".equals(detail.getQcResult()))
                    .count();
            if (count == 0) {
                qcOrderRespVO.setQcResult(QcOrderResultEnum.ALL_GOOD.getType());
            } else if (count < qcOrderDetailList.size()) {
                qcOrderRespVO.setQcResult(QcOrderResultEnum.DEFECT_EXISTS.getType());
            } else if (count == qcOrderDetailList.size()) {
                qcOrderRespVO.setQcResult(QcOrderResultEnum.ALL_DEFECT.getType());
            }
        });
        return qcOrderRespVOPageResult;
    }

    /**
     * 创建新托盘和RFID
     * @param warehouseId
     * @param deptId
     * @param infoCode
     * @return
     */
    private TrayDO createTrayAndRfid(Long warehouseId, Long deptId, String infoCode) {
        TrayDO trayDO = trayService.getTray(infoCode);
        if (trayDO != null) {
            return trayDO;
        }
        RfidDO rfidDO = rfidService.getRfid(infoCode);
        if (rfidDO != null) {
            trayDO = trayService.getTrayByRfidCode(infoCode);
            if (trayDO != null) {
                return trayDO;
            }
        }
        String trayCode = "";
        do {
            trayCode = "T" + infoCode + RandomUtil.generateDigitRandom();
        } while (trayService.getTray(trayCode) != null);
        if (rfidDO == null) {
            RfidSaveReqVO rfidSaveReqVO = new RfidSaveReqVO();
            rfidSaveReqVO.setTrayCode(trayCode);
            rfidSaveReqVO.setCode(infoCode);
            rfidSaveReqVO.setLowBattery(false);
            rfidSaveReqVO.setWarehousingTime(LocalDateTime.now());
            rfidSaveReqVO.setDeptId(deptId);
            rfidService.createRfid(rfidSaveReqVO);
        } else {
            rfidDO.setTrayCode(trayCode);
            rfidService.updateRfid(BeanUtils.toBean(rfidDO, RfidSaveReqVO.class));
        }
        TraySaveReqVO traySaveReqVO = new TraySaveReqVO();
        traySaveReqVO.setRfidCode(infoCode);
        traySaveReqVO.setCode(trayCode);
        traySaveReqVO.setWarehouseId(warehouseId);
        traySaveReqVO.setDeptId(deptId);
        traySaveReqVO.setWarehousingTime(LocalDateTime.now());
        traySaveReqVO.setStatus(WarehouseStatusEnum.ENTRY.getStatus());
        Long trayId = trayService.createTray(traySaveReqVO);
        return trayService.getTray(trayId);
    }

    /**
     * 创建质检历史记录
     *
     * @param qcOrderDetail
     * @param qcOrderCode
     * @param warehouseId
     * @param operator
     * @param qcHistorySaveReqVOList
     */
    private static void buildQcHistoryList(QcOrderDetailSaveReqVO qcOrderDetail, String qcOrderCode, Long warehouseId, Long deptId, String operator, List<QcHistorySaveReqVO> qcHistorySaveReqVOList, String boxCode) {
        QcHistorySaveReqVO qcHistorySaveReqVO = new QcHistorySaveReqVO();
        qcHistorySaveReqVO.setRfidCode(qcOrderDetail.getInfoCode());
        qcHistorySaveReqVO.setBoxCode(QcResultEnum.GOOD.getType().equals(qcOrderDetail.getQcResult()) ? boxCode : null);
        qcHistorySaveReqVO.setMaterialCode(qcOrderDetail.getMaterialCode());
        qcHistorySaveReqVO.setMaterialName(qcOrderDetail.getMaterialName());
        qcHistorySaveReqVO.setMaterialModeCode(qcOrderDetail.getMaterialModeCode());
        qcHistorySaveReqVO.setQcResult(qcOrderDetail.getQcResult());
        qcHistorySaveReqVO.setQcOrderCode(qcOrderCode);
        qcHistorySaveReqVO.setWarehouseId(warehouseId);
        qcHistorySaveReqVO.setDeptId(deptId);
        qcHistorySaveReqVO.setOperator(operator);
        qcHistorySaveReqVOList.add(qcHistorySaveReqVO);
    }

    private void updateTripartiteFifoTime(String boxCode) {
        List<MaterialDO> materialDOS = materialService.getMaterialListByBoxCode(boxCode);
        LocalDateTime firstFifoTime = null;
        for (MaterialDO materialDO : materialDOS) {
            if (firstFifoTime == null || materialDO.getFifoTime().isBefore(firstFifoTime)) {
                firstFifoTime = materialDO.getFifoTime();
            }
        }
        LambdaUpdateWrapper<MaterialDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(MaterialDO::getBoxCode, boxCode).set(MaterialDO::getTripartiteFifoTime, firstFifoTime);
        materialMapper.update(updateWrapper);
    }
}
